package org.tulkas.util;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public abstract class HttpUtil {
	private static Pattern PTN_URL_SCHEMA = Pattern.compile("(\\w+):/+");
	private static Pattern PTN_CHARSET_IN_CONTENT_TYPE = Pattern.compile(
			".*?\\s*;\\s*?charset\\s*=\\s*([\\w\\-]+)",
			Pattern.CASE_INSENSITIVE);
	private static Pattern PTN_MEDIA_TYPE = Pattern.compile("[\\w]+/[\\w+.-]+",
			Pattern.CASE_INSENSITIVE);

	/**
	 * @param s
	 * @param sb
	 * @return
	 */
	private static String parseName(String s, StringBuffer sb) {
		sb.setLength(0);
		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(i);
			switch (c) {
			case '+':
				sb.append(' ');
				break;
			case '%':
				try {
					sb.append((char) Integer.parseInt(
							s.substring(i + 1, i + 3), 16));
					i += 2;
				} catch (NumberFormatException e) {

					// need to be more specific about illegal arg
					throw new IllegalArgumentException();
				} catch (StringIndexOutOfBoundsException e) {
					String rest = s.substring(i);
					sb.append(rest);
					if (rest.length() == 2)
						i++;
				}

				break;
			default:
				sb.append(c);
				break;
			}
		}
		return sb.toString();
	}

	/**
	 * Parse the queryString in the URL into a map. The query String should be in the format of key=value&key=value&key=value..
	 * 
	 * @param s, the query string
	 * @return the map which represents the queryString, the key is a String, value is either a String or a String[] (when the same key used more than once in the queryString)
	 */
	public static Map<String, Object> parseQueryString(String s) {

		String valArray[] = null;

		if (s == null) {
			throw new IllegalArgumentException();
		}
		Map<String, Object> ht = new HashMap<String, Object>();
		StringBuffer sb = new StringBuffer();
		StringTokenizer st = new StringTokenizer(s, "&");
		while (st.hasMoreTokens()) {
			String pair = (String) st.nextToken();
			int pos = pair.indexOf('=');
			if (pos == -1) {
				// should give more detail about the illegal argument
				throw new IllegalArgumentException();
			}
			String key = parseName(pair.substring(0, pos), sb);
			String val = parseName(pair.substring(pos + 1, pair.length()), sb);
			if (ht.containsKey(key)) {
				Object oldVal = ht.get(key);
				String oldVals[] = null;
				if (oldVal instanceof String[]) {
					oldVals = (String[]) ht.get(key);
				} else {
					oldVals = new String[] { (String) ht.get(key) };
				}

				valArray = new String[oldVals.length + 1];
				for (int i = 0; i < oldVals.length; i++)
					valArray[i] = oldVals[i];
				valArray[oldVals.length] = val;
				ht.put(key, valArray);
			} else {
				ht.put(key, val);
			}
		}
		return ht;
	}

	/**
	 * Get the root Url of a full Url. <BR/>
	 * e.g. the root Url of http://www.aaa.com/bbb/ccc.html is http://www.aaa.com<BR/>
	 * the source fullUrl should be in the correct format. The schema could not be omitted.
	 * 
	 * @param fullUrl
	 * @return
	 */
	public static String getRootUrl(String fullUrl) {
		try {
			URL urlObj = new URL(fullUrl);
			URL rootObj = new URL(urlObj.getProtocol(), urlObj.getHost(),
					urlObj.getPort(), "");
			return rootObj.toString();
		} catch (MalformedURLException e) {
			return null;
		}
	}

	/**
	 * Parse the media type string from a content type string. <BR/>
	 * e.g. In the content type string: text/html; charset=UTF-8, the media type string is text/html
	 * 
	 * @param contentType
	 * @return
	 */
	public static String parseMediaTypeFromContentType(String contentType) {
		if (contentType == null)
			return null;
		Matcher matcher = PTN_MEDIA_TYPE.matcher(contentType);
		if (matcher.find())
			return matcher.group();
		return null;
	}

	/**
	 * Parse the charset string from a content type string. <BR/>
	 * e.g. In the content type string: text/html; charset=UTF-8, the charset string is UTF-8
	 * 
	 * @param contentType
	 * @return
	 */
	public static String parseCharsetFromContentType(String contentType) {
		if (contentType == null)
			return null;
		Matcher matcher = PTN_CHARSET_IN_CONTENT_TYPE.matcher(contentType);
		if (matcher.find() && matcher.groupCount() == 1)
			return matcher.group(1);
		return null;
	}

	/**
	 * attach the parameters (in the format of key=value&key=value&...) to the end of URL.
	 * It will decide whether the URL has parameters or not. Then use the appropriate '?' or '&'
	 * 
	 * @param url
	 * @param parameters
	 * @return
	 */
	public static String attachParametersToUrl(String url, String parameters) {
		if (url == null)
			return parameters;
		if (parameters == null)
			return url;
		int idx = url.indexOf('?');
		if (idx == -1) {
			return url + "?" + parameters;
		} else if (idx != url.length() - 1) {
			return url + "&" + parameters;
		} else {
			return url + parameters;
		}
	}

	/**
	 * check all fill the url into the standard format. <BR/>
	 * e.g. For the source url www.aaa.com, the returned URL would be http://www.aaa.com
	 * 
	 * @param url
	 * @return
	 * @throws MalformedURLException
	 */
	public static String checkAndFillHttpUrl(String url)
			throws MalformedURLException {
		return checkAndFillHttpUrl(url, null);
	}

	/**
	 * check all fill the url into the standard format based on a context URL. <BR/>
	 * e.g. For the source url: ccc/ddd.html, and the fromUrl: http://www.aaa.com/bbb/, the returned URL would be http://www.aaa.com/bbb/ccc/ddd.html
	 * 
	 * @param url
	 * @param fromUrl
	 * @return
	 * @throws MalformedURLException
	 */
	public static String checkAndFillHttpUrl(String url, String fromUrl)
			throws MalformedURLException {
		if (url == null)
			return null;
		if (fromUrl != null) {
			URL urlObj = new URL(new URL(fromUrl), url);
			return urlObj.toString();
		} else {
			Matcher matcher = PTN_URL_SCHEMA.matcher(url);
			if (matcher.find() && matcher.start() == 0)
				return url;
			else
				return "http://" + url;
		}
	}

	/**
	 * get the schema of a url.
	 * e.g. For the source url: file:///c:/a/b/file.ext. the returned schema is: file
	 * 
	 * @param url
	 * @return
	 * @throws MalformedURLException
	 */
	public static String getUrlSchema(String url) throws MalformedURLException {
		return getUrlSchema(url, null);
	}

	/**
	 * get the Schema of a url, based on a context url.
	 * e.g. For the source url: ccc/ddd.doc, and the fromUrl: ftp://ftp.aaa.com/aab, the returned schema is: ftp.
	 * For the source url: file:///c:/a/b/file.ext, and the from Url: null, the returned schema is: file
	 * 
	 * @param url
	 * @param fromUrl
	 * @return
	 * @throws MalformedURLException
	 */
	public static String getUrlSchema(String url, String fromUrl)
			throws MalformedURLException {
		if (url == null)
			return null;
		String fullUrl = url;
		if (fromUrl != null) {
			URL urlObj = new URL(new URL(fromUrl), url);
			fullUrl = urlObj.toString();
		}

		Matcher matcher = PTN_URL_SCHEMA.matcher(fullUrl);
		if (matcher.find() && matcher.groupCount() == 1) {
			return matcher.group(1);
		} else
			return null;
	}

	/**
	 * omit the parameters of a url and return the parts without parameters.
	 * e.g. For the source url: http://aaa.bbb.com/ccc.doc?p1=v1&p2=v2. the returned url is http://aaa.bbb.com/ccc.doc.
	 * 
	 * @param url
	 * @return
	 */
	public static String getUrlWithoutParams(String url) {
		if (url == null)
			return null;

		int index = url.indexOf('?');
		if (index == -1)
			return url;
		else {
			return url.substring(0, index);
		}
	}
}
